#!/usr/bin/env python

import os
import sys
import string
import traceback
import signal
import pytoss
import platform
import struct
import textwrap
from pytoss._users import UserManager
from pytoss._tasks import TaskManager
from pytoss._tasks import TaskExecutionError
from pytoss._receipts import ReceiptManager
from pytoss._receipts import TextualReceipt
from pytoss._flags import FlagManager
from pytoss._flags import Flag
import pytoss._io
from shell import TossShell

_print_stderr = lambda msg: pytoss._io.write_stderr(msg + "\n")

#global pytoss flags
Flag("dry-run").explain("shows the sequence of commands without actually executing them")
Flag("interactive").explain("(BETA) runs the interactive pytoss shell")

_SMALLEST_TERMINAL_SIZE = 60
terminal_cols = 0

def attempt_to_update_window_width(signum, frame):
    global terminal_cols
    
    if platform.system() != "Windows":
        try:
            import termios, fcntl

            s = struct.pack("HHHH", 0, 0, 0, 0)
            fd_stdout = sys.stdout.fileno()
            x = fcntl.ioctl(fd_stdout, termios.TIOCGWINSZ, s)
            terminal_cols = max(struct.unpack("HHHH", x)[1], _SMALLEST_TERMINAL_SIZE)
        except:
            terminal_cols = _SMALLEST_TERMINAL_SIZE
    else:
        try:
            # TODO: test this on windows
            #borrowed from http://code.activestate.com/recipes/440694/
            from ctypes import windll, create_string_buffer

            # stdin handle is -10
            # stdout handle is -11
            # stderr handle is -12

            h = windll.kernel32.GetStdHandle(-12)
            csbi = create_string_buffer(22)
            res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)

            (bufx, bufy, curx, cury, wattr,
             left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw)
            terminal_cols = max(right - left + 1, _SMALLEST_TERMINAL_SIZE)
        except:
            terminal_cols = _SMALLEST_TERMINAL_SIZE
    
def print_description(task_name, desc):
    max_len = TaskManager.get_instance().len_of_longest_task_name()
    
    desc_paragraphs = [""]
    current_paragraph = 0
    for line in desc.split("\n"):
        #if we have two line breaks, start a new paragraph
        if not line.strip():
            desc_paragraphs.append("")
            current_paragraph += 1
        else:
            desc_paragraphs[current_paragraph] += line.strip() + " "
    
    toss_name_width = min(max_len, terminal_cols * 1/4)
    desc_column_width = terminal_cols - toss_name_width - 7
    
    subsequent_indent = " " * (toss_name_width + 9) + "# "
    header_string = "  toss %%-%ss  # " % toss_name_width % task_name
    
    message = ""
    for index, paragraph in enumerate(desc_paragraphs):

        # we have to handle extra-long header strings
        if len(task_name) > toss_name_width and index == 0:
            message += "  toss %s\n" % (task_name)
        
        if len(task_name) <= toss_name_width and index == 0:
            message += textwrap.fill(
                paragraph, 
                width=terminal_cols, 
                initial_indent=header_string,
                subsequent_indent=subsequent_indent,
            )
        else:
            if index != 0:
                message += "\n%s\n" % subsequent_indent
            message += textwrap.fill(
                paragraph, 
                width=terminal_cols, 
                initial_indent=subsequent_indent,
                subsequent_indent=subsequent_indent,
            )
    
    #make sure we print the task name even if we don't have a description
    if message.strip() == "#":
        _print_stderr(header_string)
    else:
        _print_stderr(message)

def setup_handlers():
    # signal handling
    def toss_terminate_signal_handler(signum, frame):
        _print_stderr("")
        # FIXME: needs to attempt to kill all in-flight streams

    signals_to_handle = [
        signal.SIGHUP,
        signal.SIGINT,
        signal.SIGQUIT,
        signal.SIGTRAP,
        signal.SIGABRT,
    ]

    for sig in signals_to_handle:
        signal.signal(sig, toss_terminate_signal_handler)
        
    attempt_to_update_window_width(signal.SIGWINCH, None)
    #register window resize handler
    signal.signal(signal.SIGWINCH, attempt_to_update_window_width)
        
def show_task_descriptions(tasks):
    root_tasks = []
    namespaced_tasks = []
    for task in tasks:
        if task.name.find(".") == -1 and task.name.find(pytoss.support.os_path_separator) == -1:
            root_tasks.append(task)
        else:
            namespaced_tasks.append(task)
    
    current_namespace = None

    for task in root_tasks:
        if task.description:
            desc = task.description
        else:
            desc = ""
        print_description(task.name, desc)

    for task in namespaced_tasks:    
        last_slash_index = task.name.rfind(pytoss.support.os_path_separator)
        if last_slash_index != -1:
            pre_namespace = task.name[0:last_slash_index]
        else:
            pre_namespace = ""
        if "." in task.name:
            new_namespace = pre_namespace + task.name.split(".")[0]
        else:
            new_namespace = pre_namespace

        if new_namespace != current_namespace:
            # new namespace
            _print_stderr("")
            current_namespace = new_namespace
        if task.description:
            desc = task.description
        else:
            desc = ""
        print_description(task.name, desc)
    
    _print_stderr("")
    explanations = FlagManager.get_instance().get_all_explanations()
    for tossflag_name in explanations:
        _print_stderr("  [--%s] %s" % (tossflag_name, explanations[tossflag_name]))

    _print_stderr("="*terminal_cols)

def init_pytoss():
    setup_handlers()
    
    if Flag("dry-run").given:
        UserManager.get_instance().force_all_users_to_use_dry_run_strategies()
    else:
        UserManager.get_instance().do_not_use_dry_run_strategies()

    # collect all tasks by importing the local tossfile
    pytoss.Tossfile( os.path.abspath( os.getcwd() ) )
    
    if Flag("interactive").given:
        TossShell().cmdloop()


def run_pytoss():
    # otherwise, consider the first argument to be a Task name
    non_flag_args = []
    for arg in sys.argv[1:]:
        if not arg.startswith("--"):
            non_flag_args.append(arg)
        
    if len(non_flag_args) >= 1:
    
        # we have a task to execute
        _print_stderr(">"*terminal_cols)
        task_name = non_flag_args[0]
        task = TaskManager.get_instance().find_task(task_name)
    
        # by default, if no receipts were specified then we create
        # a TextualReceipt on stdout
        if len( ReceiptManager.get_instance().receipts ) == 0:
            default_receipt = TextualReceipt(sys.stderr)
            ReceiptManager.get_instance().add_receipt( default_receipt )
    
        if task:
            try:
                try:
                    task.execute()
                    _print_stderr("<"*terminal_cols)
                    _print_stderr("success!")
                except TaskExecutionError:
                    _print_stderr("<"*terminal_cols)
                    _print_stderr("failed, check the receipt for details")
            except:
                _print_stderr("*** fatal pytoss error")
                backtrace = traceback.format_tb(sys.exc_info()[2])
                _print_stderr("\n%s: %s" % (sys.exc_info()[0], sys.exc_info()[1]))
                _print_stderr("".join(backtrace))
                sys.exit(1)

            sys.exit(0)
        
        else:
            _print_stderr("<"*terminal_cols)
            _print_stderr("no task exists by the name '%s'" % task_name)
            sys.exit(1)
        
    else:
    
        # no task given, show the list of tasks
        _print_stderr("="*terminal_cols)
    
        tasks = TaskManager.get_instance().get_all_tasks()
    
        #TossShell(tasks).cmdloop()
        
        show_task_descriptions(tasks)

        sys.exit(0)
